// 将main.js里业务相关的代码抽取到app/index.js这里
// node的核心模块，放最上面
const path = require("path");

// 第三方的模块，放中间
const Koa = require("koa");
const { koaBody } = require("koa-body");
const KoaStatic = require("koa-static");
const KoaParameter = require("koa-parameter");
const { koaSwagger } = require("koa2-swagger-ui");

// 自定义的模块，放最下面
const errorHandler = require("./errorHandler");
const router = require("../router");
const { ALLOWED_MIME_TYPES, ALLOWED_EXTENSIONS } = require("../constant/allow.type");

const app = new Koa();

/**
 * 配置 Swagger UI 可视化接口文档界面
 * 通过 koa2-swagger-ui 中间件实现，用于展示和调试API
 */
app.use(
  koaSwagger({
    /**
     * 配置 Swagger UI 的访问路径
     * 示例：设置为 "/swagger" 时，可通过 http://localhost:端口/swagger 访问
     * 建议使用独立路径（如/swagger），避免与业务接口路由冲突
     */
    routePrefix: "/swagger",
    /**
     * Swagger UI 的核心配置选项
     * 用于指定文档来源、界面展示方式等
     */
    swaggerOptions: {
      /**
       * 指定 Swagger 文档的数据源（JSON格式）
       * 需指向通过 swagger-jsdoc 生成的接口文档地址
       * 此处与后端路由中定义的 /swagger.json 接口保持一致
       */
      url: "/swagger.json",
      /**
       * 接口文档默认展开方式
       * "list"：仅展开接口列表，不显示详情
       * "full"：完全展开所有接口的详细信息
       * "none"：默认全部折叠，需手动点击展开
       */
      docExpansion: "list",
      /**
       * 默认的模型渲染方式
       * "model"：展示模型的属性详情
       * "example"：展示模型的示例数据
       */
      defaultModelRendering: "model",
      /**
       * 允许在界面中编辑并重新发送的请求方法
       * 配置后，对应方法的接口可在UI中直接调试
       */
      supportedSubmitMethods: ["get", "post", "put", "delete", "patch"],
    },
  }),
);

console.log(process.cwd());
/**
 * 注册koaBody中间件
 * koa-body 是一个功能完善的 Koa 中间件，主要作用是解析 HTTP 请求体（request body），支持多种数据格式，具体包括：解析 JSON 格式请求体、解析表单 - urlencoded 格式请求体、解析 multipart 格式请求体
 * koaBody本身就是一个函数，一定要在所有路由处理之前注册这个中间件
 * 使用文档：https://www.npmjs.com/package/koa-body
 */
app.use(
  koaBody({
    multipart: true, // 是否允许上传文件？true: 允许上传（可以上传多个文件）; false: 不允许上传（默认）
    // formidable是一些关于multipart文件上传的详细配置
    formidable: {
      /**
       * 在配置项中，不推荐使用相对路径，推荐使用绝对路径
       * 在配置项里的相对路径，不是相对当前文件，而是相对node.js的执行目录，即process.cwd()
       * process.cwd() 是当前执行node命令时候的文件夹地址 —— 工作目录（这里是 koa2-api 目录）
       */
      // uploadDir: "./src/upload", // 配置上传过来的文件所放置的目录（相对路径，不推荐）
      // __dirname 是被执行的 js 文件的地址 —— 文件所在目录
      uploadDir: path.join(__dirname, "../upload"), // 配置上传过来的文件所放置的目录（绝对路径，推荐使用）
      keepExtensions: true, // 是否保留上传文件的后缀名
      /**
       * 过滤不符合条件的文件（每上传1个文件，该钩子就执行1次，独立校验单个文件）
       * 【核心机制】filter 是 formidable 处理文件的第一个钩子，优先级高于 onFileBegin，是文件“进入处理流程”的第一道校验
       *
       * 1. 若返回 false（文件不符合条件）：
       *    - 直接阻断当前文件的后续处理，文件不会写入磁盘（无临时文件生成）；
       *    - 后续与该文件相关的钩子（如 onFileBegin、onFile、onEnd）均不会触发；
       *    - 注意：仅阻断当前文件，不影响其他文件的校验（其他文件仍会触发自己的 filter）。
       *
       * 2. 若返回 true（文件符合条件）：
       *    - 文件会进入后续处理流程，后续钩子（如 onFileBegin）会正常触发；
       *    - 建议：同类校验（如 MIME 类型、扩展名）无需在 onFileBegin 等钩子重复执行，避免冗余逻辑。
       *
       * @param {Object} file - formidable 生成的文件对象（非“流”，原注释“fileStream”命名略有偏差，实际包含文件元信息+流）
       * @param {string} file.mimetype - 文件的 MIME 类型（如 image/jpeg）
       * @param {string} file.originalFilename - 文件原始文件名（含扩展名）
       */
      filter: function (file) {
        // console.log("file=", file);
        const { mimetype, originalFilename } = file;
        // 校验MIME类型
        const isMimeTypeAllowed = ALLOWED_MIME_TYPES.includes(mimetype);
        // 校验文件扩展名
        const ext = path.extname(originalFilename).toLowerCase();
        const isExtAllowed = ALLOWED_EXTENSIONS.includes(ext);
        // 日志记录
        if (!isExtAllowed || !isMimeTypeAllowed) {
          console.error(`不允许的文件格式: 扩展名=${ext}, MIME=${mimetype}, 文件名=${originalFilename}`);
        }
        return isMimeTypeAllowed && isExtAllowed;
      },
      /**
       * 文件开始写入磁盘前触发（仅对通过 filter 校验的文件生效，每1个合格文件执行1次）
       * 【核心时机】在文件流开始写入磁盘前执行，此时文件尚未生成，适合修改文件存储路径、重命名等预处理操作。
       *
       * @param {string} key - 文件对应的表单字段名（如前端 <input type="file" name="avatar"> 中的 "avatar"）
       * @param {Object} file - formidable 生成的文件对象（包含文件元信息和存储配置）
       * @param {string} file.path - 文件默认存储路径（可在此钩子修改，如自定义文件名：file.path = 新路径）
       * @param {string} file.originalFilename - 文件原始文件名
       * @param {number} file.size - 文件大小（字节，此时可能尚未完全获取，建议在 onFile 或 onEnd 中使用）
       *
       * 【典型用途】
       * - 自定义文件存储路径（如按日期分目录：./upload/202410/xxx.jpg）
       * - 重命名文件（如避免文件名重复：uuid + 扩展名）
       * - 记录文件开始处理的日志
       */
      onFileBegin: function (key, file) {
        // console.log("onFileBegin1======", key);
        // console.log("onFileBegin2======", file);
      },
      // 处理上传错误
      onError: function (err) {
        console.error("文件上传错误:", err);
      },
    },
    /**
     * 配置需要解析请求体的HTTP方法
     * - 严格模式（默认开启，无需额外配置）下，默认值为["POST", "PUT", "PATCH"]，这些方法的请求体将被解析到ctx.request.body
     * - 此处添加"DELETE"，使DELETE请求的body也能被解析（默认情况下DELETE请求body不会被解析）
     *
     * 注意：
     * 1. 严格模式（默认开启，无需额外配置）下，GET、HEAD请求的body始终不会被解析，即使在parsedMethods里配置了（符合HTTP规范）
     * 2. 解析DELETE请求body需要服务端和客户端约定，部分客户端可能不支持DELETE携带body
     * 3. 解析后的body数据会挂载到ctx.request.body，而非ctx.body
     */
    parsedMethods: ["POST", "PUT", "PATCH", "DELETE"],
  }),
);
/**
 * 注册静态资源中间件
 * 配置静态资源目录，当访问的url是静态资源的路径时，会自动去指定目录找文件
 * 如访问图片：http://localhost:8000/3da2c85386e83d929aa694001.png
 */
app.use(KoaStatic(path.join(__dirname, "../upload")));
/**
 * 注册参数检验中间件
 * 注册该中间件后，会在 Koa 上下文（ctx）的原型上添加 verifyParams 方法，
 * 用于校验请求参数（query、body、params 等）的格式和合法性。
 * 必须在所有路由处理中间件之前注册，以确保路由处理前完成参数校验。
 */
app.use(KoaParameter(app));

/**
 * 统一注册所有模块的路由
 * 使用app.use()方法注册中间件
 * app.use()方法只能接收一个函数作为参数（这个函数就是中间件），且调用完use()后会返回app本身
 * 中间件函数有参数：ctx、next
 * 参数ctx是context的缩写，它记录了所有http的上下文
 */
app.use(router.routes());
/**
 * 使用allowedMethods()判断我们定义的路由所支持的http请求类型，
 * 如果请求不支持的类型时报相应的错误，提示用户不支持该请求类型。
 * allowedMethods() 只需在最终挂载到 app 的主路由上调用一次，即可覆盖所有子路由。
 */
app.use(router.allowedMethods());

/**
 * 监听触发的错误事件，对错误进行统一处理
 */
app.on("error", errorHandler);

module.exports = app;
